<?php
/**
 * Middleware between mysql database and the RAIL.
 * Provide a basic interface to mysql and a login method for RAIL. The class has methods to get the rail_root and the current
 * loged in rail_user. Provides a disconnect method and some debug methods.
 *
 * Also define some constants with basic informations about this rail version.
 *
 * PHP versions 5
 *
 * Copyright (C)
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of ERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software
 * Fundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2008-05-04
 * @package rail
 */

// includes
require_once( 'rail_types.conf.php' );
require_once( 'rail_mimetypes.conf.php' );

// define some global constants
define( 'PHPrail_AUTHOR', 'Bastian Schröder <bs@chemanet.de>' );
define( 'PHPrail_VERSION', '1.2' );
define( 'PHPrail_REVISION', 1 );
define( 'PHPrail_BASEDIR', dirname( __FILE__ ) . '/' );


/**
 * rail autoload function.
 * Search for a PHP class file in PHPrail_BASEDIR directory.
 *
 * @param string pClassname
 * @return void
 */
function rail_autoload ( $pClassname ) {
	$file =  strtolower($pClassname) . '.class.php';

	// return if not requested a rail class file
	if( substr( $file, 0, 5 ) != 'rail_' )
		return;

	// rail_exception
	if( strrpos( $file, '_exception' ) ){
		require_once( 'exceptions/' . $file );
		return;
	}

	// search in base dir
	if( is_file( PHPrail_BASEDIR . $file ) )
		require_once( $file );
	// search in objects dir
	else if( is_file( PHPrail_BASEDIR . 'objects/' . $file ) )
		require_once( 'objects/' . $file );
}
spl_autoload_register ('rail_autoload');

class rail_connector {

	/**
	 * MySQL database connector.
	 *
	 * @var mysqli
	 */
	public $mysqli;

	/**
	 * Current loged in rail_user.
	 *
	 * @var rail_user
	 */
	private $rail_user;

	/**
	 * The rail_root
	 *
	 * @var rail_root
	 */
	private $rail_root;

	/**
	 * Count number of database requests for debug reason.
	 *
	 * @var rail_root
	 */
	private $request_counter = 0;


	/**
	 * Constructor
	 * Handle MySQL connection and login special rail_user 'guest'.
	 *
	 * @exception rail_exception
	 * @param string $pDatabase MySQL database name
	 * @param string $pUser MySQL user
	 * @param string $pPasswd MySQL password
	 * @param string $pServer MySQL server (optional)
	 */
	public function __construct ( $pDatabase, $pUser, $pPasswd, $pServer = 'localhost' ) {
		// connect to mysql server
		$this->mysqli = new mysqli( $pServer, $pUser, $pPasswd );
		if ( mysqli_connect_errno() )
			throw new rail_exception( null, mysqli_connect_error() );

		// select table
		$this->mysqli->select_db( $pDatabase );
		if ( $this->mysqli->errno )
			throw new rail_exception( null, "Can't access databases: '$pDatabase'" );

		// check wheater databases is a RAIL tree
		$data = $this->mysqli->query( 'SHOW tables;' );
		$search_array = array( 'objects', 'objects_attributes');
		$i = 0;
		while( $data_row = $data->fetch_assoc() )
			if( in_array($data_row["Tables_in_$pDatabase"], $search_array) ) $i ++;
		if( $i != count($search_array) )
			throw new rail_exception( null, "Databases '$pDatabase' dosn't contains a RAIL tree." );

		// hard login guest user. get user with username guest and the lowesed rail_id.
		$guest_id = $this->send_request(
			"SELECT
				objects.id
			 FROM
			 	objects,objects_attributes
			 WHERE
			 	objects.id = objects_attributes.object AND objects.`type`='RAIL_USER' AND
				objects_attributes.`attribute` = 'OBJ_NAME' AND objects_attributes.`value` = 'guest'
			ORDER BY
				`id` DESC
			LIMIT 1;"
		)->fetch_assoc();
		$this->rail_user = new rail_user( $this, $guest_id[ 'id' ] );
	}


	/**
	 * Disconnected from RAIL.
	 *
	 */
	public function disconnect () {
		$this->mysqli->close();
		unset( $this->mysqli );
		unset( $this->rail_user );
		unset( $this->rail_root );
	}


	/**
	 * Get number of database requests. Helpful to optimize the count of MySQL requests.
	 *
	 * @return int
	 */
	public function get_request_count () {
		return $this->request_counter;
	}


	/**
	 * Get the rail_root object.
	 *
	 * @return rail_root
	 */
	public function get_root () {
		if( !$this->rail_root instanceof rail_object ){
			$data = $this->send_request( "SELECT id FROM objects WHERE `environment`='0' AND `type`='RAIL_ROOT' LIMIT 1" )->fetch_assoc();
			$this->rail_root = new rail_root( $this, $data[ 'id' ] );
		}

		return $this->rail_root;
	}


	/**
	 * Get current loged in rail_user.
	 *
	 * @return rail_user
	 */
	public function get_user () {
		return $this->rail_user;
	}


	/**
	 * Try to connect with a rail_user to RAIL.
	 *
	 * @exception rail_exception
	 * @param  rail_user $pUser
	 * @param string $pPasswd
	 */
	public function login( $pUser, $pPasswd ) {
		$data = $this->send_request( "
			SELECT
				objects.id,objects_attributes.`attribute`,objects_attributes.`value`
			 FROM
			 	objects,objects_attributes
			 WHERE
			 	objects.id = objects_attributes.`object`
			 	AND objects.`type`='RAIL_USER'
			 	AND (
			 		( objects_attributes.`attribute` = 'OBJ_NAME' AND objects_attributes.`value` = '". trim( $pUser ) . "' )
			 		OR objects_attributes.`attribute` = 'RAIL_PASSWD'
			 	)
		" );

		// get object id and passwd from data
		while( $data_row = $data->fetch_assoc() ){
			if( ( $data_row[ 'attribute' ] != 'OBJ_NAME' && !isset( $id ) ) || ( isset( $id ) && $data_row[ 'id' ] != $id ) )
				continue;

			if( $data_row[ 'attribute' ] == 'OBJ_NAME' )
				$id = $data_row[ 'id' ];
			else
				$passwd = $data_row[ 'value' ];
		}

		// throw exceptions if user with this passwd dosn't exist.
		if( !isset( $id ) || $passwd != md5( $pPasswd ) )
			throw new rail_exception( null, 'Access denied: password or user failure!' );

		$this->rail_user = new rail_user( $this, $id );
	}


	/**
	 * Send an MySQL multi_query to database and return results as array. Return a single result object if
	 * the query contains only one result. Increment the request counter and wrap all MySQL error with a
	 * rail_exception.
	 * This Method is public but can only call from a rail class.
	 *
	 * @exception rail_exception
	 * @param string $pQuery MySQL multi_query
	 * @return mixed
	 */
	public function send_request( $pQuery ){
		// check wheather this method is called by a rail class (intern)
		#$stack = debug_backtrace();
		#if( count( $stack ) < 2 || substr( $stack[ 1 ][ 'class' ], 0, 5 ) != 'rail_' )
		#	throw new rail_exception( null, 'it\'s a bad idear to call send_request() directly.' );

		$results = array();

		// get mysqli query results
		if( $this->mysqli->multi_query( $pQuery ) ){
			do{
				if( $result = $this->mysqli->store_result() )
					$results[] = $result;
			} while( $this->mysqli->next_result() );
		}
		if ( mysqli_errno( $this->mysqli ) )
			throw new rail_exception( null, mysqli_error( $this->mysqli ) );

		// increment debug counter
		$this->request_counter ++ ;

		return count( $results ) == 0 ? $this->mysqli->insert_id : ( count( $results ) <= 1 ? $results[ 0 ] : $results );
	}

} // End of rail_connector
?>